﻿

namespace Bootstrap5.Abstractions;

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;

using Bootstrap5.Design;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Rendering;
using Microsoft.Extensions.Options;
using Microsoft.JSInterop;

/// <summary>
/// Respresents the component base using Bootstrap framework.
/// </summary>
public abstract class BootstrapComponentBase : ComponentBase,IAsyncDisposable
{
    #region Injection
    /// <summary>
    /// The injected instance of <see cref="ICssClassBuilder"/> .
    /// </summary>
    [Inject]protected ICssClassBuilder CssClassBuilder { get; set; }
    /// <summary>
    /// The injected instance of <see cref="BootstrapTriggerBuilder"/> .
    /// </summary>
    [Inject]protected BootstrapTriggerBuilder TriggerBuilder {  get; set; }
    /// <summary>
    /// The injected instance of <see cref="IOptions{BootstrapOptions}"/>
    /// </summary>
    [Inject] IOptions<BootstrapOptions> Options { get; set; }
    /// <summary>
    /// 获取注入可操作 JS 的对象。
    /// </summary>
    [Inject] protected IJSRuntime JS { get;private set; }
    #endregion

    #region Parameters
    [Parameter] [HtmlProperty("id")] public string Id { get; set; }
    /// <summary>
    /// 其他属性。
    /// </summary>
    [Parameter(CaptureUnmatchedValues = true)] public IDictionary<string, object> AdditionalAttributes { get; set; } = new Dictionary<string, object>();

    /// <summary>
    /// 外边距 margin。
    /// </summary>
    [Parameter]public ISpacingDecorator Margin { get; set; }
    /// <summary>
    /// 内边距 padding。
    /// </summary>
    [Parameter]public ISpacingDecorator Padding { get; set; }
    /// <summary>
    /// 追加 Css 常用类。
    /// </summary>
    [Parameter]public ICssUtilityDecorator CssClass { get; set; }
    /// <summary>
    /// 附加的 Css 类。
    /// </summary>
    [Parameter] public string AdditionalCssClass { get; set; }

    /// <summary>
    /// Sets the text of 'aria-label' attribute.
    /// </summary>
	[Parameter][HtmlProperty("aria-label")] public string AriaLabel { get; set; }
    /// <summary>
    /// Sets the bootstrap trigger as 'data-bs-*'.
    /// </summary>
    [Parameter] public TriggerProvider Trigger { get; set; }
	#endregion

	#region Protected Property
    /// <summary>
    /// Gets the module task of bootstrap.js to import.
    /// </summary>
	protected Lazy<Task<IJSObjectReference>> BootstrapModuleTask { get; private set; }
    /// <summary>
    /// Gets the <see cref="BootstrapOption"/> configured.
    /// </summary>
    protected BootstrapOptions BootstrapOption => Options?.Value;
    
    #endregion


    /// <summary>
    /// Initialized the instance of <see cref="BootstrapComponentBase"/> class.
    /// </summary>
    protected BootstrapComponentBase()
    {
        BootstrapModuleTask = new(() => JS.InvokeAsync<IJSObjectReference>("import", BootstrapOption.BoostrapJsSrc).AsTask());
    }

    /// <summary>
    /// Get the 'class' as string.
    /// </summary>
    /// <returns>A string seperated by space for each item.</returns>
    public string GetCssClassString()
    {
        if (AdditionalAttributes.TryGetValue("class", out object cssClass))
        {
            return cssClass.ToString();
        }

        ResolveCssClassAttribute();
        CreateCssClass(CssClassBuilder);

        Margin?.BuildClass(CssClassBuilder);
        Padding?.BuildClass(CssClassBuilder);
        BuildExtraCssClassProvider(CssClassBuilder);

        CssClass?.BuildClass(CssClassBuilder);
        if (AdditionalCssClass is not null)
        {
            CssClassBuilder.Add(AdditionalCssClass);
        }

        return CssClassBuilder.ToString();
    }

    protected virtual void BuildExtraCssClassProvider(ICssClassBuilder builder)
    {
    }

    protected virtual void CreateCssClass(ICssClassBuilder builder)
    {

    }

    protected override void BuildRenderTree(RenderTreeBuilder builder)
    {
        var tagName = GetHtmlTagName();
        builder.OpenElement(0, tagName);

        BuildCommonAttributes(builder);
        BuildChildContent(builder);

        builder.CloseElement();
    }

    protected virtual void BuildCommonAttributes(RenderTreeBuilder builder)
    {
        BuildClassAttribute(builder);
        BuildExtraAttributes(builder);
        ResolveHtmlPropertyAttribute(builder);
        ResolveDataTriggerAttribute();
        BuildBootstrapTriggers(TriggerBuilder);
        BuildEventCallbackAttribute(builder);
        BuildAdditionalAttributes(builder);
    }

    private void BuildEventCallbackAttribute(RenderTreeBuilder builder)
    {
        if (this is IHasOnClick click)
        {
            builder.AddAttribute(100, "onclick", EventCallback.Factory.Create(this, click.OnClick));
        }
    }

    protected void BuildAdditionalAttributes(RenderTreeBuilder builder, int sequence = 2) 
        => builder.AddMultipleAttributes(sequence, AdditionalAttributes);

    protected void BuildClassAttribute(RenderTreeBuilder builder, int sequence = 1)
    {
        var cssClassString = GetCssClassString();
        if (!string.IsNullOrEmpty(cssClassString))
        {
            builder.AddAttribute(sequence, "class", cssClassString);
        }
    }

    protected virtual void BuildBootstrapTriggers(BootstrapTriggerBuilder builder)
    {

    }

    protected virtual void BuildChildContent(RenderTreeBuilder builder)
    {
        if (this is IHasChildContent childContent)
        {
            builder.AddContent(120, childContent.ChildContent);
        }
    }

    /// <summary>
    /// Override <see cref="GetHtmlRole"/> method or define <see cref="HtmlRoleAttribute"/> for component class to build the <c>role="xxx"</c> html attribute.
    /// </summary>
    /// <param name="builder">The instance of <see cref="RenderTreeBuilder"/>.</param>
    /// <param name="sequence">The sequence of attribute.</param>
    protected virtual void BuildHtmlRoleAttribute(RenderTreeBuilder builder, int sequence = 30)
    {
        var roleName = GetHtmlRole();
        if (GetType().TryGetCustomeAttribute<HtmlRoleAttribute>(out var roleAttribute))
        {
            roleName = roleAttribute.Name;
        }
        AdditionalAttributes.Add("role", roleName);
    }

    protected virtual string GetHtmlRole() => string.Empty;

    protected virtual void BuildExtraAttributes(RenderTreeBuilder builder,int sequence=5)
    {

    }

    private void ResolveCssClassAttribute()
    {
        var componentType = GetType();

        if (componentType.IsDefined(typeof(CssClassAttribute)))
        {
            CssClassBuilder.Add(componentType.GetCustomAttribute<CssClassAttribute>().Name);
        }

        var prefix = componentType.GetCustomAttribute<CssClassPrefixAttribute>()?.Prefix;

        foreach (var property in componentType
            .GetProperties())
        {
            if (!property.CanRead)
            {
                return;
            }


            var propertyValue = property.GetValue(this);
            string className=string.Empty;
            if (propertyValue == null)
            {
                if(property.TryGetCustomeAttribute<NullCssClassAttribute>(out var nullCssClass))
                {
                    className = nullCssClass.Name;
                }
            }
            else
            {
                var cssClassAttribute = property.GetCustomAttribute<CssClassAttribute>();
                if(cssClassAttribute is null)
                {
                    continue;
                }
                className = cssClassAttribute.Name;
                if (propertyValue.GetType() == typeof(bool))
                {
                    var boolValue = (bool)propertyValue;
                    if (!boolValue)
                    {
                        className = null;
                    }
                }
                else if (propertyValue.GetType().BaseType == typeof(Enum))
                {
                    propertyValue = propertyValue.GetEnumCssClass();
                    className = $"{className}{propertyValue}";
                }
                else if (typeof(CssEnumeration).IsAssignableFrom(propertyValue.GetType().BaseType))
                {
                    var cssEnum= (propertyValue as CssEnumeration);
                    if(cssEnum is null)
                    {
                        return;
                    }
                    className = $"{className}{cssEnum.Value}";
                }
            }
            if (!string.IsNullOrEmpty(className))
            {
                CssClassBuilder.Add($"{prefix}{className}");
            }
        }
    }

    /// <summary>
    /// Resolve the <see cref="HtmlPropertyAttribute"/> from parameters to add into the html attributes.
    /// </summary>
    /// <param name="builder">The instance of <see cref="RenderTreeBuilder"/>.</param>
    /// <param name="startSequence">the starting sequence.</param>
    private void ResolveHtmlPropertyAttribute(RenderTreeBuilder builder,int startSequence=50)
    {
        var componentType = GetType();

        var index = 0;
        foreach (var property in componentType
            .GetProperties()
            .Where(m => m.IsDefined(typeof(HtmlPropertyAttribute))))
        {
            if (!property.CanRead)
            {
                return;
            }


            var propertyValue = property.GetValue(this);
            var propertyName = property.GetCustomAttribute<HtmlPropertyAttribute>()?.Name;

            builder.AddAttribute(startSequence + index, propertyName, propertyValue);
            index++;
        }
    }

    void ResolveDataTriggerAttribute()
    {
        var componentType = GetType();

        foreach (var property in componentType
    .GetProperties().Where(
            m=>m.IsDefined(typeof(DataTriggerAttribute)) 
    || m.IsDefined(typeof(DataTriggerBooleanAttribute)) 
    || m.IsDefined(typeof(DataTriggerToggleAttribute))
    ))
        {
            var propertyValue = property.GetValue(this);

            var triggerName = string.Empty;

            if (property.TryGetCustomeAttribute<DataTriggerAttribute>(out var triggerAttribute))
            {
                triggerName = triggerAttribute.Name;
            }

            if (propertyValue is null)
            {
                propertyValue = triggerAttribute?.NullValue;
            }
            else if (propertyValue is bool boolValue )
            {
                if (boolValue && property.TryGetCustomeAttribute<DataTriggerToggleAttribute>(out var toggleTriggerAttribute))
                {
                    TriggerBuilder.SetData("toggle", toggleTriggerAttribute.Value);
                }
                if (property.TryGetCustomeAttribute<DataTriggerBooleanAttribute>(out var boolTriggerAttribute))
                {
                    triggerName = boolTriggerAttribute.Name;
                    propertyValue = boolValue ? boolTriggerAttribute.TrueValue : boolTriggerAttribute.FalseValue;
                }
            }
            if (!string.IsNullOrEmpty(triggerName))
            {
                TriggerBuilder.SetData(triggerName, propertyValue);
            }
        }

        Trigger?.BuildTrigger(TriggerBuilder);

        var triggers = TriggerBuilder.Triggers;
        var index = 0;
        foreach (var item in triggers)
        {
            AdditionalAttributes[$"data-bs-{item.Key}"] = item.Value;
            index++;
        }
    }

    /// <summary>
    /// Get the html tag name, default is 'div' or set <see cref="HtmlTagAttribute"/> for component class.
    /// </summary>
    /// <returns>The html element name.</returns>
    protected virtual string GetHtmlTagName() => GetType().GetCustomAttribute<HtmlTagAttribute>()?.Name ?? "div";

    public async ValueTask DisposeAsync()
    {
        CssClassBuilder.Dispose();

        if (BootstrapModuleTask.IsValueCreated)
        {
            var module = await BootstrapModuleTask.Value;
            await module.DisposeAsync();
        }
    }
}
